/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.misc.rmc.support.sap.rfc.jco;

import com.sap.conn.jco.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vacoor.nothing.common.reflect.Parameter;
import org.vacoor.nothing.common.util.StringUtils;
import org.vacoor.nothing.misc.sap.jco.annotation.Func;
import org.vacoor.nothing.misc.sap.jco.annotation.Param;
import org.vacoor.nothing.misc.sap.jco.annotation.Table;
import org.vacoor.nothing.misc.sap.jco.binding.MapperMethod;
import org.vacoor.nothing.misc.sap.jco.binding.Session;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * RFC 映射方法执行类
 *
 * @author vacoor
 */
public class JcoMapperMethod<T> extends MapperMethod<T> {
    private static final Logger logger = LoggerFactory.getLogger(JcoMapperMethod.class);

    protected Class<?> mapperInterface;
    protected Method method;
    protected Session session;

    public JcoMapperMethod(Class<?> mapperInterface, Method method, Session session) {
        super(mapperInterface, method, session);
    }

    public Object execute(Object... args) throws Throwable {
        Class<?> retType = method.getReturnType();
        boolean retVoid = Void.class.isAssignableFrom(retType);
        boolean retMany = Iterable.class.isAssignableFrom(retType) || retType.isArray();
        boolean retMap = Map.class.isAssignableFrom(retType);
        boolean retOne = !retVoid && !retMany && !retMap;

        // RFC 函数名称
        Func func = method.getAnnotation(Func.class);
        String funcName = func == null || func.value().isEmpty() ? method.getName() : func.value();

        // 结果表名称
        String tabName = null;
        if (!retVoid) {     // 如果有结果集, 获取 table name
            Table table = method.getAnnotation(Table.class);
            if (table == null) {
                String msg = "can't find " + Table.class.getName() + " from " + method;
                RuntimeException e = new RuntimeException(msg);
                logger.error(msg, e);
                throw e;
            }
            tabName = table.value();
        }

        logger.debug("readed function name: {} and table name: {} from {}", funcName, tabName, method);

        // 获取 RFC 目的
        final JCoDestination dest = session.getDestination();
        // 获取 RFC 函数对象及输入参数对象
        JCoFunction jcoFunc = dest.getRepository().getFunction(funcName);
        JCoParameterList jcoImpParams = jcoFunc.getImportParameterList();

        Parameter[] parameters = Parameter.lookup(method);
        // 遍历所有方法参数, 设置参数
        for (Parameter p : parameters) {
            int index = p.getIndex();
            Object value = args[index];

            Param param = p.getAnnotation(Param.class);
            /** 如果没有 {@link org.vacoor.nothing.misc.sap.jco.annotation.Param} 则通过索引设置 */
            if (param == null) {
                jcoImpParams.setValue(index, value);
                logger.debug("设置索引参数 - {} = {}", index, value);
            } else {
                String name = param.value();
                jcoImpParams.setValue(name, value);
                logger.debug("设置命名参数 - {} = {}", name, value);
            }
        }

        // 执行 RFC 函数
        logger.debug("call function {}", funcName);
        jcoFunc.execute(dest);
        logger.debug("call function {} successfull", funcName);

        if (retVoid) {  // 如果返回值类型为 void 不需要获取结果
            return null;
        }

        /******* 结果处理 *******/
        /* 可以遍历所有结果表, 发现总有一张 RETURN
        for (JCoField field : jcoFunc.getTableParameterList()) {
            System.out.println(field.getName() + "=" + field.getString() + " : " + field.isTable());
            JCoTable tab = jcoFunc.getTableParameterList().getTable(field.getName());
        }
        */
        JCoTable jcoTab = jcoFunc.getTableParameterList().getTable(tabName);
        JCoRecordMetaData metaData = jcoTab.getRecordMetaData();

        Object result = null;
        if (retMany) {
            result = executeForMany(jcoTab, metaData);
        } else if (retMap) {    // 返回 map
            throw new UnsupportedOperationException("does not support the return map object");
        } else {    // 其余情况认为返回单一对象
            throw new UnsupportedOperationException("does not support the return single object");
        }

        /* 暂时不支持返回单一值
        // 返回 null 但是 返回类型是非 void 基本类型
        if (result == null && retType.isPrimitive() && !retVoid) {

        }
        */
        return result;
    }

    /**
     * 处理多条记录结果
     */
    protected Object executeForMany(JCoTable table, JCoRecordMetaData metaData) throws Throwable {
        List result = new LinkedList();
        int fieldCount = metaData.getFieldCount();

        Type type = method.getGenericReturnType();

        // 获取具体结果元素类型
        Class<?> elementType;
        // 获取泛型参数, 上面已经判断, array, iterator 为 many 因此这里只获取第一个泛型参数
        if (type instanceof ParameterizedType) {
            Type ar = ((ParameterizedType) type).getActualTypeArguments()[0];
            if (!Class.class.isAssignableFrom(ar.getClass())) {
                throw new IllegalStateException("unable to get results of specific types");
            }
            elementType = Class.class.cast(ar);
        } else if (type instanceof Class && ((Class<?>) type).isArray()) {
            elementType = ((Class<?>) type).getComponentType();
        } else {
            throw new IllegalStateException("unable to get results of specific types");
        }

        // 遍历所有结果行
        while (table.nextRow()) {
            // 封装结果
            Object rowObj = elementType.newInstance();

            // 遍历结果列
            for (int i = 0; i < fieldCount; i++) {
                String name = metaData.getName(i);
                Object value = table.getValue(name);  // 这里 JCO 已经转换为相应类型, 爽呀

                /*
                char[] chars = name.toLowerCase().toCharArray();
                chars[0] = Character.toUpperCase(chars[0]);
                Method setter = elementType.getMethod("set" + String.valueOf(chars), value.getClass());
                */
                Method setter = elementType.getMethod("set" + StringUtils.underscoreToCamelCase(name, true), value.getClass());
                setter.invoke(rowObj, value);
            }

            result.add(rowObj);
        }

        // 如果返回类型 不是 List
        Class<?> retType = method.getReturnType();
        if (!retType.isAssignableFrom(result.getClass())) {
            if (retType.isArray()) {
                // convert to array
                return toArray(result);
            } else {
                // convert to many type
                Class<?> clazz = resolveInterface(retType);
                throw new UnsupportedOperationException("only support array, list, collection, iterator result type now.");
            }
        }

        return result;
    }
}
